#include "MyHeader.h"

/*Declare our function prototypes*/
void InitializeSprites(void); // Init the sprites (data, attributes)
void SetupFirstSprite(void); // Setup the 1st sprite (launcher)
void WaitForVsync(void); // Keep synchronous sprites processing
void CopyOAM(void); // Copy sprites array to OAM (real memory)
void MoveSprite(OAMEntry* sp, int x, int y); // Move the sprite to coordination (x,y)
void GetInput(void); // Get and Process keyboard input
void InterrupHandler(void); //For Timer 2
void KeyHandler(); //

/* Game mode*/// _ is global === l is local
u8 _gameMode; /*0 is manual, 1 is automated mode*/
u8 _ufosNum;//Number of Ufo - trace
u8 _score;// initiate score
u8 _speed; //_speed increase, speed increase and delay decrease
u8 _isPaused; // 0: playing or 1: paused
u8 _layer; // which layer is chosen -> writeText

/* Attribute of Launcher (8x16) */
s16 x = 100; // 8 pixels for launcher's width and 80 for text display
s16 y = 160 - 16; // 16 pixels for launcher's length

/* Attributes of Rocket (8x8) */
s16 xRocket; // x of Rocket
s16 yRocket; // y of Rocket
u16 _stateOfRocket = 0; // State of Rocket : 0 - fixed, 1: moving

/*For checking launcher movement state*/
u16 _isLauncherXIncreased = 1; // Flag for proper launcher movement: +x -x
s8 _timeRemained; // time decrease gradually

/*Looping variable*/
int i;

/* Init value for Ufos - 16 x 16*/
int xUfos[15];
int yUfos[15];
int _myTimer;
u16 _soundLength[] = { 3, 8, 1, 2 };// Length of all sound in file sound.h. Sound.h has 4 variable: Sound1Data, Sound2Data, Sound3Data, Sound4Data.
u16 _soundLengthInSec;//Length of sound
u16 _soundPlayedInSec; //Number of second played the sound

/*Main entry point*/
int main(void) {
	Setup();

	//=======//
	GAMEINTRO:
	PlaySoundDMA(1);
	ShowIntro();
	//loop DMA Sound
	while (1) {
		if (_soundPlayedInSec > _soundLengthInSec)
			PlaySoundDMA(1);
		if (key_hit(KEY_START)) {
			StopDMASound();
			goto GAMEINIT;
		}
		key_poll();
	}

	//=======//
	GAMEINIT:
		PlaySoundDMA(4);
		Initialization();

	//=======//
	PLAYING:
	while (!_isPaused && _ufosNum > 0 && _timeRemained > 0) {
		_myTimer++;
		key_poll();// Call to set value for Key_Current &  Key_Previous
		KeyHandler();//Handler for keys
		WaitForVsync(); // Wait for the screen to stop drawing
		UpdatePosition();

		MoveSprite(&sprites[0], x, y);
		MoveSprite(&sprites[1], xRocket, yRocket);
		CheckTarget(); // Check whether Ufo is shot

		WriteNum(27, 9, _ufosNum, _layer);
		WriteNum(27, 11, _score, _layer);
		WriteNum(26, 7, _timeRemained, _layer);
		CopyOAM(); //Copy our sprites array into OAM.
	}

	if (!_isPaused) {
		InitializeSprites();
		CopyOAM();

		//If all Ufos shot
		if (_ufosNum == 0) {
			PlaySoundDMA(2);
			ClearText(_layer);
			WriteText(7, 10, "Mission Completed!_", _layer);
		} else {
			PlaySoundDMA(3);
			ClearText(_layer);
			WriteText(10, 10, "GAME OVER_", _layer);
		}

		while (1) {
			key_poll();
			if (key_hit(KEY_START)) {
				goto GAMEINIT;
			}
		}
	}

	//=======//
	PAUSED:
	while (_isPaused) {
		key_poll();
		if (key_hit(KEY_B)) //X: B
			_isPaused = 0;
		
	}
	WriteText(20, 17, "       _", _layer);
	goto PLAYING;
}

void Setup() {
	_layer = 2; // use the BG2
	/*Set GBA Graphics MODE
	 Mode 0: 4 background regular (no rotation, no scale)
	 Enable sprites and 1D Sprite: tiles are are stored sequentially */
	SetMode(MODE_0 | OBJ_ENABLE | OBJ_MAP_1D | BG2_ENABLE);
	REG_BG2CNT = 0x1000 | 128;//16..23 (start address for BG2) | 256 Color

	//Timer: Don't use Timer 0 & 1 => Use for direct sounds (mandatory)
	REG_TM2D = 0xC000; //FFFF (65535) - (10^6/61.04)  timer // Init value for timer 2
	// Count 1 time takes 61.04 us. How many counts it takes for 10^6 us?  (16383 times [equal to 1 s])
	// FFFF - 16383 = C000 (Count from C000 to FFFF)
	REG_TM2CNT = TIME_ENABLE | TIME_FREQUENCY_1024 | TIME_IRQ_ENABLE;
	//TIME_IRQ_ENABLE: register publisher for timer 2

	//Interrupt
	REG_INTERUPT = (u32) InterrupHandler; //Register interrupt handler
	REG_IE = INT_TIMER2; //REG_IE: register lister for timer 2
	REG_IME = 1; // enable master interrupt

	//Copy 2 bytes at one time - 1 item of launcherData represents 2 bytes - 2 pixels
	//Image = 8x16 pixel. 1 pixel need 1 byte for expressing 256 colors. 8x16= 128 bytes => copy 128/2 items
	//Bmp2sprt => generate all 256 colors: launcherPalette => master palette
	DMA3Copy((u32)launcherPalette,(u32) OBJPaletteMem, 256, 0); // Palette for all sprites (master palette)

	// Copy sprites' data: 0 (default settings)
	DMA3Copy((u32)launcherData,(u32) OAMData, lSizeLauncher/2 , 0
			| DMA_ENABLE | DMA_SOURCE_INCREMENT | DMA_DEST_INCREMENT | DMA_TIMING_IMMEDIATE); //launcherData: 64
	DMA3Copy((u32)rocketData,((u32) (OAMData+(lMaxSizeLauncher/2))), lSizeRocket/2, 0);//rocketData: 32
	DMA3Copy((u32)ufoData,((u32) (OAMData+lMaxSizeLauncher/2+lMaxSizeRocket/2)), lSizeUFO/2, 0);//ufoData: 128
	DMA3Copy((u32)logovguData,((u32) (OAMData+lMaxSizeLauncher/2+lMaxSizeRocket/2 +lMaxSizeUFO/2)), lSizeLogoVGU/2, 0);//logovguData: 1024
	DMA3Copy((u32)flagvnData, ((u32) (OAMData+lMaxSizeLauncher/2+lMaxSizeRocket/2 +lMaxSizeUFO/2 + lMaxSizeLogo/2)), lSizeFlagVN/2, 0);//flagvnData: 256
	DMA3Copy((u32)flaggermanData,((u32) (OAMData+lMaxSizeLauncher/2+lMaxSizeRocket/2 +lMaxSizeUFO/2 + lMaxSizeLogo/2 + lMaxSizeFlagVN/2)), lSizeFlagGerman/2, 0);//flaggermanData: 256

	//For Background
	DMA3Copy((u32)tiles_Palette,(u32) BGPaletteMem, 256, 0); //Palette for Background (Texts)
	DMA3Copy((u32)tiles_Data,(u32) VideoBuffer, 2688, 0); //VRAM: Background (Texts)

	//Init sprites data
	InitializeSprites();
}
void ShowIntro() {
	//Sprite university Logo (64x32) attributes: 120 first sprites for gaming
	sprites[121].attribute0 = COLOR_256 | 0x4000 | 10;
	sprites[121].attribute1 = 0xC000 | 88; //(240-64)/2
	sprites[121].attribute2 = 2 // 2: 256 colors
			* (lMaxSizeLauncher + lMaxSizeRocket + lMaxSizeUFO) / 64;

	//Sprite Flag vn attributes
	sprites[122].attribute0 = COLOR_256 | 0x4000 | 44;
	sprites[122].attribute1 = 0x8000 | 88;
	sprites[122].attribute2 = 2 * (lMaxSizeLauncher + lMaxSizeRocket + lMaxSizeUFO
			+ lMaxSizeLogo) / 64;

	//Sprite Flag german attributes
	sprites[123].attribute0 = COLOR_256 | 0x4000 | 44;
	sprites[123].attribute1 = 0x8000 | 121;
	sprites[123].attribute2 = 2 * (lMaxSizeLauncher + lMaxSizeRocket + lMaxSizeUFO
			+ lMaxSizeLogo + lMaxSizeFlagGer) / 64;
	WaitForVsync();
	CopyOAM();
	ClearText(_layer);
	WriteText(1, 12, "Two Countries, One Mission:_", _layer);
	WriteText(9, 14, "Shooting UFO_", _layer);
}

void Initialization() {
	_gameMode = 1; /*0 is manual, 1 is automated mode*/
	_score = 0;// initiate score
	_speed = 1;
	_isPaused = 0;
	_ufosNum = _maxUfos;
	_myTimer = 0;

	//randomize Ufos position: rand() % 10 => 1 random int (0-9)
	int lTemp = 0;
	for (i = 0; i < _maxUfos; i++) {
		lTemp += rand() % 10 + 8; // x2 is always bigger than x1
		xUfos[i] = lTemp;
		yUfos[i] = rand() % 50;
	}

	/*Set-up all 128 sprites to offscreen*/
	InitializeSprites();
	//**Set property
	/*Set-up the first sprite (Launcher)*/
	sprites[0].attribute0 = COLOR_256 | 0x8000 | y;
	sprites[0].attribute1 = 0x0000 | x;
	/* Sprite is represented by tiles. Start at title 0.
	 * Tile 0 is the first sprite data memory location.*/
	sprites[0].attribute2 = 0;//First tile

	//Rocket sprite
	yRocket = y - 8;
	xRocket = x;
	sprites[1].attribute0 = COLOR_256 | 0x0000 | yRocket;
	sprites[1].attribute1 = 0x0000 | ROTDATA(0) | xRocket;
	sprites[1].attribute2 = 2 * (lMaxSizeLauncher / (8 * 8)); //8*8 = 64 | 2 (use tile mode 256 colors)

	//Sprite UFO attributes
	for (i = 0; i < _maxUfos; i++) {
		sprites[i + 2].attribute0 = COLOR_256 | 0x0000 | yUfos[i];
		sprites[i + 2].attribute1 = 0x4000 | xUfos[i];
		sprites[i + 2].attribute2 = 2 * (lMaxSizeLauncher + lMaxSizeRocket) / 64;
	}

	//Sprite Logo attributes
	sprites[121].attribute0 = COLOR_256 | 0x4000 | 1;
	sprites[121].attribute1 = 0xC000 | 160;
	sprites[121].attribute2 = 2
			* (lMaxSizeLauncher + lMaxSizeRocket + lMaxSizeUFO) / 64;

	//Sprite Flag vn attributes
	sprites[122].attribute0 = COLOR_256 | 0x4000 | 34;
	sprites[122].attribute1 = 0x8000 | 160;
	sprites[122].attribute2 = 2 * (lMaxSizeLauncher + lMaxSizeRocket + lMaxSizeUFO
			+ lMaxSizeLogo) / 64;

	//Sprite Flag german attributes
	sprites[123].attribute0 = COLOR_256 | 0x4000 | 34;
	sprites[123].attribute1 = 0x8000 | 192;
	sprites[123].attribute2 = 2 * (lMaxSizeLauncher + lMaxSizeRocket + lMaxSizeUFO
			+ lMaxSizeLogo + lMaxSizeFlagGer) / 64;
	WaitForVsync();
	CopyOAM();

	ClearText(_layer); // Clear background
	_timeRemained = _maxTimeCounter;
	WriteText(20, 7, "Time :_", _layer);
	WriteNum(26, 7, _timeRemained, _layer);

	WriteText(20, 9, "UFO  :_", _layer);
	WriteNum(27, 9, _ufosNum, _layer);

	WriteText(20, 11, "Score:_", _layer);
	WriteNum(27, 11, _score, _layer);

	WriteText(20, 13, "Speed:_", _layer);
	WriteNum(27, 13, 6 - _speed, _layer);

	if (_gameMode)
		WriteText(20, 15, "Mode :Auto  _", _layer);
	else
		WriteText(20, 15, "Mode :Man._", _layer);
}

void UpdatePosition() { // all sprites
	//In Automatic mode
	if (_gameMode == 1) {
		Delay((_speed - 1) * 1500); // Thread.sleep()
		// 3 movements: launcher, rocket, ufo
		if (_isLauncherXIncreased) {
			x++;
			if (x > 152) {
				_isLauncherXIncreased = 0;
				x--;
			}
		} else {
			x--;

			if (x < 1) {
				x = 1;
				_isLauncherXIncreased = 1;
			}
		}
	}

	//Changes sprite attributes based on new x,y
	//Rocket Ready for shouting
	if (_stateOfRocket == 0) {
		xRocket = x;
		yRocket = y - 8;
	}
	//Rocket moving
	else {
		yRocket -= 3;
		if (yRocket < 0) {
			_stateOfRocket = 0;
		}
	}

	int j;
	if (_myTimer % 5 == 0) // 5 times = 1 time update
		//Ufos adjust position
		for (j = 0; j < _maxUfos; j++)
			if (xUfos[j] < 160) {
				xUfos[j] += (2 - (rand() % 5));
				if (xUfos[j] < 0)
					xUfos[j] = 0;
				if (xUfos[j] > 148)
					xUfos[j] = 148;

				yUfos[j] += (2 - (rand() % 5));
				if (yUfos[j] < 0)
					yUfos[j] = 1;
				if (yUfos[j] > 50)
					yUfos[j] = 50;
				MoveSprite(&sprites[j + 2], xUfos[j], yUfos[j]);
			}
}

//Set sprites to offscreen
void InitializeSprites(void) {
	int i;
	for (i = 0; i < 128; i++) {
		sprites[i].attribute0 = 160; //y to > 159
		sprites[i].attribute1 = 240; //x to > 239
	}
}

//Wait for the screen to stop drawing
void WaitForVsync(void) {
	while (REG_VCOUNT < 160);
}

///Copy our sprite array to OAM
void CopyOAM(void) {
	u16 i;
	u16* temp;
	temp = (u16*) sprites; // Cast to u16
	for (i = 0; i < 128 * 4; i++) { // 4 attributes
		OAM[i] = temp[i];
	}
}

void CheckTarget() {
	int i;
	int lDistance;
	for (i = 0; i < _maxUfos; i++) {
		//If rocket & Ufo meet together.
		lDistance = xRocket < xUfos[i] ? 8 : 16; // Left or Right
		if (abs(yRocket - yUfos[i]) < 8 && abs(xRocket - xUfos[i]) < lDistance) {
			//move sprite out
			MoveSprite(&sprites[i + 2], 240, 160); // Sprite of Ufo starts from 2
			xUfos[i] = 241; //Move x out of touch
			_stateOfRocket = 0;
			_ufosNum--;
			_score++;
			return;
		}
	}
}

//Move the sprite: Affect on the array (attributes)
void MoveSprite(OAMEntry* sp, int x, int y) {
	if (x < 0) x = 0;
	if (y < 0) y = 0;

	// Processing Y
	sp->attribute0 = sp->attribute0 & 0xFF00; //clear the old y value: 7 bits end 0
	sp->attribute0 = sp->attribute0 | y;
	// Processing X
	sp->attribute1 = sp->attribute1 & 0xFE00; //clear the old x value: 8 bits end 0
	sp->attribute1 = sp->attribute1 | x;
}

//**Interrupt Handler
void InterrupHandler() {
	REG_IME = 0; //Disable interrupt register
	if (REG_IF & INT_TIMER2) { // If timer 2 issues this interrupt
		_soundPlayedInSec++;
		if (_soundPlayedInSec > _soundLengthInSec) {
			StopDMASound();
		}
		REG_IF |= INT_TIMER2; // have already processed the interrupt of timer 2 (INT_TIMER2)
		dprint("Timer2 Handle;");

		if (_timeRemained > 0 & !_isPaused) {
			_timeRemained--;
		}
	}
	REG_IME = 1; //Enable Interrupt register
}

//Key Handler
void KeyHandler() {
	//Key Up - Shooting
	if (key_hit(KEY_UP)) {
		if (_stateOfRocket == 0) {
			_stateOfRocket = 1;
			PlaySound1();
		}
	}

	if (!(*KEYS & KEY_DOWN)) {}

	//Key Left - Move Launcher to the left
	if (!(*KEYS & KEY_LEFT)) {
		if (_gameMode == 0) {
			x--;
			if (x < 0)
				x = 0;
		}
	}

	//Key Right - Move Launcher to the right
	if (!(*KEYS & KEY_RIGHT)) {
		if (_gameMode == 0) {
			x++;
			if (x > 152)
				x = 152;
		}
	}

	///**********For Demo key Only***************
	if (!(*KEYS & KEY_A)) { // key_hit: flag
	
		
		//Key A - Game mode: Auto=1; Manual=0
	//if (key_hit(KEY_A)) { // key_hit: flag
		_gameMode = 1 - _gameMode;
		if (_gameMode)
			WriteText(20, 15, "Mode :Auto  _", _layer);
		else
			WriteText(20, 15, "Mode :Man._", _layer);
	}

	//Key B - Pause - play
	if (key_hit(KEY_B)) { //X
		_isPaused = 1 - _isPaused;
		if (_isPaused)
			WriteText(20, 17, "Paused _", _layer);
	}

	//Key R - Speed setup
	if (key_hit(KEY_R)) { //S
		if (_gameMode == 1 && !_isPaused) {
			_speed++;
			if (_speed > 5)
				_speed = 0;
			WriteNum(27, 13, 6 - _speed, _layer);
		}
	}
	
}

void Delay(long num) {
	long i;
	for (i = 0; i < num; i++);
}

void PlaySound1() {
	//Turn on sound circuit - Master sound enable and enable Sound 1-4
	REG_SOUNDCNT_X = 0x80;

	//Sound 1-4 Output level and Stereo control
	//Full volume, enable sound 1 to both left and right
	REG_SOUNDCNT_L = 0x1177;

	// Overall: output ratio
	REG_SOUNDCNT_H = 2;

	// Configure for sound channel 1
	REG_SOUND1CNT_L = 0x0046; //Sound 1 Sweep control //Low
	REG_SOUND1CNT_H = 0xfAF7; //Sound 1 Length, wave duty and envelope control //High
	REG_SOUND1CNT_X = 0xCEAD; //Sound 1 Frequency, reset and loop control //Frequency
}

//****
//Modified from: http://belogic.com/gba/ - Direct sound
//****
//soundNum: 4 songs
void PlaySoundDMA(u8 soundNum) {
	_soundPlayedInSec = 1; // number of seconds has passed
	_soundLengthInSec = _soundLength[soundNum - 1]; // total length in seconds of this song

	//Play a mono sound at 16khz
	//Use timer 0 as sampling rate source
	
	REG_SOUNDCNT_H = 0x0B0F; //0x0b0F; //enable DS A&B + FIFO reset + use timer0 + max volume to L and R
	REG_SOUNDCNT_X = 0x0080; //turn sound chip on
	switch (soundNum) {
	case 1:
		REG_DMA1SAD = (u32) _Song1Data; // src
		break;
	case 2:
		REG_DMA1SAD = (u32) _Song2Data;
		break;
	case 3:REG_DMA1SAD = (u32) _Song3Data;
		break;
	case 4:
		REG_DMA1SAD = (u32) _Song4Data;
		break;
	}

	//DMA1 destination
	REG_DMA1DAD = 0x040000a0; //write to FIFO A address 
	REG_DMA1CNT_H = 0xb600; //DMA control: DMA enabled+ start on FIFO+32bit+repeat+increment source&dest

	//REG_TM0D
	//Formula for playback frequency is: 0xFFFF-round(cpuFreq/playbackFreq) 
	REG_TM0CNT_L =  0xFBE6; //16khz playback frequency
	REG_TM0CNT_H = 0x0080; //enable timer
}

///Stop timer0 & DMA1
void StopDMASound() {
	REG_TM0CNT_H = 0; //disable timer 0
	REG_DMA1CNT_H = 0x00; //stop DMA
	dprint("Stop DMA Sound");
}
